#include "socket.h"

#include <arpa/inet.h>
#include <stdint.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>

#include <cstdio>
#include <cerrno>

using namespace std;

const int Socket::LISTEN_Q_LENGTH = 100;

Socket::Socket() {
  sock_fd_ = -1;
}

Socket::~Socket() {
  if (sock_fd_ > 0) {
    this->Close();
  }
}

bool Socket::Create() {
  sock_fd_ = socket(AF_INET, SOCK_STREAM, 0);
  if (sock_fd_ < 0) {
    return false;
  }

  int on = 1;
  if (setsockopt(sock_fd_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
    return false;
  } 

  return true;
}

bool Socket::Bind(uint16_t port) {
  if (sock_fd_ < 0) {
    return false;
  }

  sock_addr_.sin_family = AF_INET; // IPv4
  sock_addr_.sin_addr.s_addr = htonl(INADDR_ANY); // Wildcard IP
  sock_addr_.sin_port = htons(port); // port

  if (bind(sock_fd_, (struct sockaddr*) &sock_addr_, sizeof(sock_addr_)) < 0) {
    return false;
  }

  return true;
}

bool Socket::Listen() {
  if (sock_fd_ < 0) {
    return false;
  }

  if (listen(sock_fd_, Socket::LISTEN_Q_LENGTH) < 0) {
    return false;
  }

  return true;
}

bool Socket::Accept(Socket& new_socket) const {
  if (sock_fd_ < 0) {
    return false;
  }

  sockaddr_in cli_addr = new_socket.sock_addr();
  socklen_t len = sizeof(cli_addr);

  int new_sock_fd = accept(sock_fd_, (struct sockaddr*) &cli_addr, &len);
  new_socket.set_sock_fd(new_sock_fd);

  if (new_sock_fd < 0) {
    return false;
  }

  return true;
}

bool Socket::Connect(const char* host, uint16_t port) {
  if (sock_fd_ < 0) {
    return false;
  }

  sock_addr_.sin_family = AF_INET;
  sock_addr_.sin_port = htons(port);
  inet_pton(AF_INET, host, &(sock_addr_.sin_addr));

  if (errno == EAFNOSUPPORT) {
    return false;
  }

  if (connect(sock_fd_, (struct sockaddr *) &sock_addr_, sizeof(sock_addr_)) < 0) {
    perror("Error Connecting");
    return false;
  }

  return true;
}

int Socket::Send(const void* buffer, size_t length) const {
  size_t bytes_sent = send(sock_fd_, buffer, length, 0);

  if (bytes_sent != length)
	  cerr << "Send() : " << length - bytes_sent << " bytes not sent" << endl;
  return bytes_sent;
}

int Socket::Receive(void* buffer, size_t length) const {
  size_t bytes_received = recv(sock_fd_, buffer, length, 0);
  if (bytes_received != length)
	  cerr << "Receive() : " << length - bytes_received << " bytes not received" << endl;
  return bytes_received;
}

bool Socket::SetReceiveTimeoutInSec(int sec) {
  struct timeval tv;
  tv.tv_sec = sec;
  tv.tv_usec = 0;
  if (setsockopt(sock_fd_, SOL_SOCKET, SO_RCVTIMEO, (char*) &tv, sizeof(tv)) < 0) {
    return false;
  }
  return true;
}

bool Socket::Close() {
  if (sock_fd_ < 0) {
    return false;
  }
  if (close(sock_fd_) < 0) {
    return false;
  }
  sock_fd_ = -1;
  return true;
}

sockaddr_in Socket::sock_addr() {
  return sock_addr_;
}

void Socket::set_sock_fd(int sock_fd) {
  sock_fd_ = sock_fd;
}

int Socket::sock_fd() {
  return sock_fd_;
}
